/**
 *  @file MemoryStream.cpp
 */
#include "MemoryStream.h"
#include "../collections/BinBuf.h"
#include "../text/CFString.h"
#include "../lang/IntCheck.h"
#include "../common/UtilFuncT.h"

using namespace cppflib::collections;

namespace cppflib
{

namespace io
{

MemoryStream::MemoryStream(void)
{
   this->rwPos = 0;
   this->pBinBuf = new BinBuf();
   cppmempool::Retain(this->pBinBuf);
}

/**
 *  init with an already filled binbuf
 */
MemoryStream::MemoryStream(BinBuf *pBinBuf)
{
   this->rwPos = 0;
   this->pBinBuf = pBinBuf;
   cppmempool::Retain(this->pBinBuf);
}

MemoryStream::~MemoryStream(void)
{
   Close();
}

void MemoryStream::Close()
{
   if (this->pBinBuf) {
      cppmempool::Release(this->pBinBuf);
      this->pBinBuf = NULL;
   }
}

void MemoryStream::Flush()
{
   // not valid for MemoryStream
}

sz_t MemoryStream::CanReadNoBlocking()
{
   return pBinBuf->GetValidLength() - rwPos;
}

int MemoryStream::Read(collections::ArrayByte &buffer)
{
   return Read(buffer, 0, static_cast<int>(buffer.GetSize()));
}

int MemoryStream::Read(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   if (rwPos >= pBinBuf->GetValidLength())
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));
   // bytes left in binbuf
   sz_t bytesRead = pBinBuf->GetValidLength() - rwPos;
   // read the smaller number
   bytesRead = cppflib::Min<sz_t>(static_cast<sz_t>(count), bytesRead);
   buffer.CopyFrom(pBinBuf->GetRawBuf(), static_cast<u32_t>(pBinBuf->GetValidLength()), 
                   offset, static_cast<u32_t>(rwPos), static_cast<u32_t>(bytesRead));
   
   rwPos += bytesRead; // update

   return static_cast<int>(bytesRead);
}

/**
 *  Read a single byte
 *
 *  @return -1 if error
 *  @return byte read
 */
int MemoryStream::Read()
{
   if (rwPos >= pBinBuf->GetValidLength())
      return -1;

   int ret = (static_cast<int>(pBinBuf->GetRawBuf()[rwPos++]) & 0xff);
   return ret;
}

/**
 *  Seek to a position within the internal buffer
 *  
 *  @return new pos after seek
 *  @return -1 if failed
 */
i64_t MemoryStream::Seek(i64_t offset, seekorigin_t origin)
{
   i64_t pos = 0;
   switch(origin) {
      case Stream::S_BEGIN: pos = 0; break;
      case Stream::S_CUR: pos = rwPos; break;
      case Stream::S_END: pos = pBinBuf->GetValidLength(); break;
   }
   
   if (lang::CheckOverflow(lang::AOP_ADD, pos, offset))
      return -1;

   pos += offset;
   if (pos < 0 || pos >= static_cast<i64_t>(pBinBuf->GetValidLength()))
      return -1;
   
   rwPos = static_cast<u32_t>(pos);
   return rwPos;
}

int MemoryStream::Write(collections::ArrayByte &buffer)
{
   return Write(buffer, 0, static_cast<int>(buffer.GetSize()));
}

/**
 *  Write bytes to file
 *
 *  @return number of bytes written
 */
int MemoryStream::Write(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));
   this->pBinBuf->Realloc(rwPos + count); // ensure buffer is big enough
   buffer.CopyTo(pBinBuf->GetRawBuf(), static_cast<u32_t>(pBinBuf->GetLength()), offset, static_cast<u32_t>(rwPos), count);

   rwPos += count; // update
   if (rwPos > pBinBuf->GetValidLength())
      pBinBuf->SetValidLength(rwPos);

   return count;
}

bool MemoryStream::Write(u8_t val)
{
   if (rwPos >= pBinBuf->GetValidLength()) {
      pBinBuf->AppendData(&val, 1);
   }
   else {
      pBinBuf->GetRawBuf()[rwPos] = val;
   }

   ++rwPos;  // update
   return true;
}

/**
 *  Reset read/write pos to beginning
 */
void MemoryStream::ResetPointer()
{
   Seek(0, Stream::S_BEGIN);
}

/**
 *  Reset pointer and clear the underlying buffer to zeros
 */
void MemoryStream::ResetPointerAndClearBuffer()
{
   ResetPointer();
   pBinBuf->ClearBuf();
}

/**
 *  Get the size with valid content
 */
sz_t MemoryStream::GetBufferSize()
{
   return pBinBuf->GetValidLength();
}


} // end of namespace io

} // end of namespace cppflib
